﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Min.ExtendKit.Common.Net.Diagnostics;

/// <summary>
/// DNS 工具类，封装常见的域名解析、反向解析、网络信息获取功能。
/// </summary>
/// <remarks>
/// 支持 IPv4 / IPv6、本机网络环境信息查询。
/// </remarks>
public static class DnsHelper
{
    #region 同步方法

    /// <summary>
    /// 执行反向 DNS 解析（IP → 域名）。
    /// </summary>
    /// <param name="ip">目标 IP 地址（如 "8.8.8.8"）。</param>
    /// <param name="onExceptionMessage">捕获异常消息。</param>
    /// <returns>解析到的主机名，失败返回 null。</returns>
    public static string? ReverseDnsLookup(string ip, Action<Exception>? onExceptionMessage = null)
    {
        try
        {
            if (IPAddress.TryParse(ip, out var ipAddr))
            {
                var entry = Dns.GetHostEntry(ipAddr);
                return entry.HostName;
            }
            return null;
        }
        catch (Exception ex)
        {
            onExceptionMessage?.Invoke(ex);
            return null;
        }
    }

    /// <summary>
    /// 执行 DNS 域名解析（域名 → IP 地址数组）。
    /// </summary>
    /// <param name="host">目标主机名。</param>
    /// <param name="onExceptionMessage">捕获异常消息。</param>
    /// <returns>
    /// 解析得到的 IP 地址数组。若解析失败则返回空数组。
    /// </returns>
    public static IPAddress[] DnsResolve(string host, Action<Exception>? onExceptionMessage = null)
    {
        try
        {
            return Dns.GetHostAddresses(host);
        }
        catch (Exception ex)
        {
            onExceptionMessage?.Invoke(ex);
            return [];
        }
    }

    #endregion

    #region 异步方法

    /// <summary>
    /// 异步执行反向 DNS 解析（IP → 域名）。
    /// </summary>
    /// <param name="ip">目标 IP 地址。</param>
    /// <param name="onExceptionMessage">捕获异常消息。</param>
    /// <returns>解析到的主机名，失败返回 null。</returns>
    public static async Task<string?> ReverseDnsLookupAsync(string ip, Action<Exception>? onExceptionMessage = null)
    {
        try
        {
            if (!IPAddress.TryParse(ip, out var ipAddr)) return null;
            var entry = await Dns.GetHostEntryAsync(ipAddr);
            return entry.HostName;
        }
        catch (Exception ex)
        {
            onExceptionMessage?.Invoke(ex);
            return null;
        }
    }

    /// <summary>
    /// 异步执行 DNS 域名解析（域名 → IP）。
    /// </summary>
    /// <param name="host">目标主机名。</param>
    /// <param name="onExceptionMessage">捕获异常消息。</param>
    /// <returns>解析得到的 IP 地址数组，失败返回空数组。</returns>
    public static async Task<IPAddress[]> DnsResolveAsync(string host, Action<Exception>? onExceptionMessage = null)
    {
        try
        {
            return await Dns.GetHostAddressesAsync(host);
        }
        catch (Exception ex)
        {
            onExceptionMessage?.Invoke(ex);
            return Array.Empty<IPAddress>();
        }
    }

    #endregion

    #region 扩展功能 

    /// <summary>
    /// 获取完全限定域名（FQDN）。
    /// </summary>
    /// <param name="host">目标主机名。</param>
    /// <returns>
    /// 完全限定域名（如 "server01.example.com"）。
    /// 若无法获取则返回原始输入。
    /// </returns>
    public static string GetFqdn(string host)
    {
        try
        {
            var entry = Dns.GetHostEntry(host);
            return entry.HostName;
        }
        catch
        {
            return host;
        }
    }

    /// <summary>
    /// 检测指定域名是否可以解析成功。
    /// </summary>
    /// <param name="host">目标主机名或域名。</param>
    /// <returns>
    /// 如果解析成功，返回 <c>true</c>；
    /// 否则返回 <c>false</c>。
    /// </returns>
    public static bool IsResolvable(string host)
    {
        return DnsResolve(host).Any();
    }

    /// <summary>
    /// 获取指定域名的首个 IPv4 地址。
    /// </summary>
    /// <param name="host">目标主机名（如 "google.com"）。</param>
    /// <returns>
    /// IPv4 地址，若未找到则返回 <c>null</c>。
    /// </returns>
    public static IPAddress? GetPrimaryIPv4(string host)
    {
        return DnsResolve(host).FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);
    }

    /// <summary>
    /// 获取指定域名的首个 IPv6 地址。
    /// </summary>
    /// <param name="host">目标主机名（如 "google.com"）。</param>
    /// <returns>
    /// IPv6 地址，若未找到则返回 <c>null</c>。
    /// </returns>
    public static IPAddress? GetPrimaryIPv6(string host)
    {
        return DnsResolve(host).FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetworkV6);
    }

    /// <summary>
    /// 获取本机当前使用的 DNS 服务器地址列表。
    /// </summary>
    /// <returns>
    /// 本机网络接口配置的 DNS 服务器 IP 地址集合。
    /// </returns>
    /// <remarks>
    /// 若电脑配置了多个网卡，可能会返回多个 DNS 地址。
    /// </remarks>
    public static IEnumerable<IPAddress> GetLocalDnsServers()
    {
        var result = new List<IPAddress>();
        foreach (var ni in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces())
        {
            if (ni.OperationalStatus != System.Net.NetworkInformation.OperationalStatus.Up) continue;
            var ipProps = ni.GetIPProperties();
            result.AddRange(ipProps.DnsAddresses);
        }
        return result.Distinct();
    }

    /// <summary>
    /// 获取本地主机名（HostName）。
    /// </summary>
    /// <remarks>
    /// 此方法返回与 <see cref="Environment.MachineName"/> 通常相同的主机名，
    /// 但底层实现不同：
    /// <list type="bullet">
    /// <item>
    /// <description>
    /// <see cref="Environment.MachineName"/>  
    /// 直接从操作系统环境变量获取本机计算机名，始终是当前计算机的 NetBIOS 名称，
    /// 性能更优，几乎不会抛出异常。
    /// </description>
    /// </item>
    /// <item>
    /// <description>
    /// <see cref="Dns.GetHostName"/>  
    /// 通过 DNS API 获取主机名，可能返回相同的 NetBIOS 名称，
    /// 但在某些网络环境下（如加入域、VPN、Linux 容器），
    /// 返回的结果可能包含 FQDN（完全限定域名）的前缀部分，
    /// 因此更适合配合 <see cref="Dns.GetHostAddresses(string)"/> 做网络相关解析。
    /// </description>
    /// </item>
    /// </list>
    /// </remarks>
    /// <returns>
    /// 返回当前机器的主机名字符串。  
    /// 在大多数情况下，该值与 <see cref="Environment.MachineName"/> 相同。
    /// </returns>
    /// <example>
    /// 示例：
    /// <code>
    /// string hostName = DnsHelper.GetLocalHostName();
    /// Console.WriteLine($"本地主机名: {hostName}");
    /// </code>
    /// </example>
    public static string GetLocalHostName() => Dns.GetHostName();

    #endregion
}

